/* Copyright (C) 2021 Rain */

/* This file is part of XNIX. */

/* 
  XNIX is free software: you can redistribute it and/or modify 
  it under the terms of the GNU General Public License as published by 
  the Free Software Foundation, either version 3 of the License, or 
  (at your option) and later version. 
*/

/*
  XNIX is distributed in the hope that it will be useful, 
  but WITHOUT ANY WARRANTY; without even the implied warranty of 
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 
  GNU General Public License for more details. 
*/

/*
  You should have received a copy of the GNU General Public License 
   along with XNIX. If not, see <https://www.gnu.org/licenses/>.  
*/




#include <kernel/init.h>
#include <kernel/errno.h>
#include <kernel/types.h>

#include <kernel/console.h>
#include <kernel/mm.h>
#include <kernel/tty.h>

#include <kernel/int.h>
#include <kernel/panic.h>

#include <arch/io.h>
#include <modules/serial.h>



struct tty tty1;


errno_t serial_init(void) {
	if (bootarg->com0) com_init(bootarg->com0);
	if (bootarg->com1) com_init(bootarg->com1);
	if (bootarg->com2) com_init(bootarg->com2);
	if (bootarg->com3) com_init(bootarg->com3);

	set_irq(3, serial1_interrupt);
	set_irq(4, serial0_interrupt);

	if (!bootarg->com0) return 0;

	tty1.input_fifo = NULL;
	tty1.mode = T_COOKED | T_NORD;

	tty1.ctl = 0;
	tty1.vx = tty1.vy = 0;

	tty1.putc = serial_putc;
	tty1.next = NULL;

	/* register a new tty if com0 exists */

	tty0.next = &tty1;

	/* com0 exists, so set it to stdout */
	stdout = &tty1;

	serial_write(bootarg->com0, serial_read(bootarg->com0));

	return 0;
}

void com_init(__io_t port) {
	outb(0x80, port + 3);
	outb(0x30, port + 0);
	outb(0x00, port + 1);

	outb(0x03, port + 3);
	outb(0x0b, port + 4);
	outb(0x0d, port + 1);

	inb(port + 0);

	return;
}


/* the tab size of serial (on qemu) */
#define SERIAL_TAB_SIZE 	8

int serial_putc(struct tty *tty, char c) {
	if (c == '\n') {
		tty->vx = 0;
		tty->vy = 0;
		
		serial_write(bootarg->com0, '\r');
		serial_write(bootarg->com0, '\n');

		return 0;
	}

	if (c == '\r') {
		tty->vx = 0;
		serial_write(bootarg->com0, '\r');

		return 0;
	}

	if (c == '\b') {
		tty->vx--;
		serial_write(bootarg->com0, '\b');

		return 0;
	}

	if (c == '\t') {
		int ax = (tty->vx + SERIAL_TAB_SIZE) & ~SERIAL_TAB_SIZE;

		if (ax == tty->vx) 
			tty->vx += SERIAL_TAB_SIZE;

		else 
			tty->vx = ax;

		serial_write(bootarg->com0, '\t');

		return 0;
	}

	serial_write(bootarg->com0, c);
	tty->vx++;

	return 0;
}


void serial_write(__io_t port, __uint8_t data) {
	while (!(inb(port + 5) & 0x20));

	outb(data, port + 0);

	return;
}

char serial_read(__io_t port) {
	/* zero if no data */
	return inb(port);
}

void serial0_interrupt(void) {
	serial_info("interrupt from serial0. \n");

	return;
}

void serial1_interrupt(void) {
	serial_info("interrupt from serial1. \n");

	return;
}


